﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Calibrator.cs" company="ScRePt">
//   Copyright © ScRePt 2010 - 2012
// </copyright>
// <summary>
//   The calibrator.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace MP3D.Calibration
{
    #region Using Directives

    using System;
    using System.Reflection;

    using MediaPortal.Dialogs;
    using MediaPortal.GUI.Library;

    using Stereoscopic;

    #endregion

    /// <summary>
    /// The calibrator.
    /// </summary>
    public class Calibrator : IDisposable
    {
        #region Constants and Fields

        /// <summary>
        /// The original offset x.
        /// </summary>
        private readonly int originalOffsetX;

        /// <summary>
        /// The original offset y.
        /// </summary>
        private readonly int originalOffsetY;

        /// <summary>
        /// The original zoom horizontal.
        /// </summary>
        private readonly float originalZoomHorizontal;

        /// <summary>
        /// The original zoom vertical.
        /// </summary>
        private readonly float originalZoomVertical;

        /// <summary>
        /// The disposing.
        /// </summary>
        private bool disposing;

        /// <summary>
        /// The layers.
        /// </summary>
        private IRenderLayer[] layers = new IRenderLayer[15];

        /// <summary>
        /// The output format.
        /// </summary>
        private int outputFormat;

        #endregion

        #region Constructors and Destructors

        /// <summary>
        /// Initializes a new instance of the <see cref="Calibrator"/> class.
        /// </summary>
        public Calibrator()
        {
            this.originalZoomHorizontal = GUIGraphicsContext.ZoomHorizontal;
            this.originalZoomVertical = GUIGraphicsContext.ZoomVertical;
            this.originalOffsetX = GUIGraphicsContext.OffsetX;
            this.originalOffsetY = GUIGraphicsContext.OffsetY;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets FirstOffsetX.
        /// </summary>
        public int FirstOffsetX { get; set; }

        /// <summary>
        /// Gets or sets FirstOffsetY.
        /// </summary>
        public int FirstOffsetY { get; set; }

        /// <summary>
        /// Gets or sets OutputFormat.
        /// </summary>
        public int OutputFormat
        {
            get
            {
                return this.outputFormat;
            }

            set
            {
                this.outputFormat = value;
                this.CalculateOffsets();
            }
        }

        /// <summary>
        /// Gets or sets SecondOffsetX.
        /// </summary>
        public int SecondOffsetX { get; set; }

        /// <summary>
        /// Gets or sets SecondOffsetY.
        /// </summary>
        public int SecondOffsetY { get; set; }

        #endregion

        #region Public Methods

        /// <summary>
        /// The start.
        /// </summary>
        public void Start()
        {
            this.HookLayers();
            GUIWindowManager.Receivers += this.OnGuiWindowManagerReceivers;
        }

        #endregion

        #region Implemented Interfaces

        #region IDisposable

        /// <summary>
        /// The dispose.
        /// </summary>
        public void Dispose()
        {
            this.disposing = true;
            GUIWindowManager.Receivers -= this.OnGuiWindowManagerReceivers;
            GUIGraphicsContext.ZoomHorizontal = this.originalZoomHorizontal;
            GUIGraphicsContext.ZoomVertical = this.originalZoomVertical;
            GUIGraphicsContext.OffsetX = this.originalOffsetX;
            GUIGraphicsContext.OffsetY = this.originalOffsetY;
            this.UnHookLayers();
        }

        #endregion

        #endregion

        #region Methods

        /// <summary>
        /// The calculate offsets.
        /// </summary>
        private void CalculateOffsets()
        {
            switch (this.OutputFormat)
            {
                case CONSTS.METHOD_SIDEBYSIDE1:
                case CONSTS.METHOD_SIDEBYSIDE2:
                    GUIGraphicsContext.ZoomHorizontal = this.originalZoomHorizontal / 2;
                    GUIGraphicsContext.ZoomVertical = this.originalZoomVertical;
                    this.FirstOffsetX = this.originalOffsetX;
                    this.FirstOffsetY = this.originalOffsetY;
                    this.SecondOffsetX = (GUIGraphicsContext.Width / 2) + (this.originalOffsetX / 2);
                    this.SecondOffsetY = this.originalOffsetY;
                    break;
                case CONSTS.METHOD_OVERUNDER1:
                case CONSTS.METHOD_OVERUNDER2:
                    GUIGraphicsContext.ZoomHorizontal = this.originalZoomHorizontal;
                    GUIGraphicsContext.ZoomVertical = this.originalZoomVertical / 2;
                    this.FirstOffsetX = this.originalOffsetX;
                    this.FirstOffsetY = this.originalOffsetY;
                    this.SecondOffsetX = this.originalOffsetX;
                    this.SecondOffsetY = (GUIGraphicsContext.Height / 2) + (this.originalOffsetY / 2);
                    break;
                default:
                    GUIGraphicsContext.ZoomHorizontal = this.originalZoomHorizontal;
                    GUIGraphicsContext.ZoomVertical = this.originalZoomVertical;
                    this.FirstOffsetX = this.originalOffsetX;
                    this.FirstOffsetY = this.originalOffsetY;
                    this.SecondOffsetX = this.originalOffsetX;
                    this.SecondOffsetY = this.originalOffsetY;
                    break;
            }
        }

        /// <summary>
        /// The get layers from reflection.
        /// </summary>
        private void GetLayersFromReflection()
        {
            FieldInfo f = typeof(GUILayerManager).GetField("_layers", System.Reflection.BindingFlags.Static | System.Reflection.BindingFlags.NonPublic);

            if (f != null)
            {
                // ReSharper disable AssignNullToNotNullAttribute
                this.layers = (IRenderLayer[])f.GetValue(null);

                // ReSharper restore AssignNullToNotNullAttribute
            }
        }

        /// <summary>
        /// The hook layer.
        /// </summary>
        /// <param name="layer">
        /// The layer.
        /// </param>
        /// <returns>
        /// </returns>
        private IRenderLayer HookLayer(IRenderLayer layer)
        {
            if (this.ShouldHookLayer(layer))
            {
                RenderStereoscopicLayer l = new RenderStereoscopicLayer { BaseClass = layer };
                layer = l;
                l.RenderLayerRequested += this.OnRenderLayerRequested;
                if (l.BaseClass is GUIWindow)
                {
                    GUIWindow w = (GUIWindow)l.BaseClass;
                    w.GetType().GetMethod("OnWindowLoaded", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(w, null);
                    w.ResetAllControls();
                }
            }

            return layer;
        }

        /// <summary>
        /// The hook layers.
        /// </summary>
        private void HookLayers()
        {
            this.GetLayersFromReflection();

            for (int i = 0; i < this.layers.Length; i++)
            {
                this.layers[i] = this.HookLayer(this.layers[i]);
            }
        }

        /// <summary>
        /// The on gui window manager receivers.
        /// </summary>
        /// <param name="message">
        /// The message.
        /// </param>
        private void OnGuiWindowManagerReceivers(GUIMessage message)
        {
            if (message.Message == GUIMessage.MessageType.GUI_MSG_ITEM_FOCUS_CHANGED || message.Message == GUIMessage.MessageType.GUI_MSG_WINDOW_INIT)
            {
                if (GUIWindowManager.GetWindow(message.TargetWindowId) is GUIDialogWindow)
                {
                    this.HookLayers();
                }
            }
        }

        /// <summary>
        /// The on render layer requested.
        /// </summary>
        /// <param name="layer">
        /// The layer.
        /// </param>
        /// <param name="timePassed">
        /// The time passed.
        /// </param>
        private void OnRenderLayerRequested(IRenderLayer layer, float timePassed)
        {
            if (this.disposing)
            {
                return;
            }

            GUIGraphicsContext.OffsetX = this.FirstOffsetX;
            GUIGraphicsContext.OffsetY = this.FirstOffsetY;
            layer.RenderLayer(timePassed);
            GUIFontManager.Present();
            GUIGraphicsContext.OffsetX = this.SecondOffsetX;
            GUIGraphicsContext.OffsetY = this.SecondOffsetY;
            layer.RenderLayer(timePassed);
        }

        /// <summary>
        /// The should hook layer.
        /// </summary>
        /// <param name="layer">
        /// The layer.
        /// </param>
        /// <returns>
        /// The should hook layer.
        /// </returns>
        private bool ShouldHookLayer(IRenderLayer layer)
        {
            return !(layer is RenderStereoscopicLayer || layer == null);
            ////return layer is GUIVideoFullscreen || layer is GUITopbar || layer is GUIDialogWindow;
        }

        /// <summary>
        /// The un hook layer.
        /// </summary>
        /// <param name="layer">
        /// The layer.
        /// </param>
        /// <returns>
        /// </returns>
        private IRenderLayer UnHookLayer(IRenderLayer layer)
        {
            IRenderLayer ret = layer;

            if (layer is RenderStereoscopicLayer)
            {
                RenderStereoscopicLayer l = (RenderStereoscopicLayer)layer;
                ret = l.BaseClass;
                l.RenderLayerRequested -= this.OnRenderLayerRequested;

                if (ret is GUIWindow)
                {
                    GUIWindow w = (GUIWindow)ret;
                    w.GetType().GetMethod("OnWindowLoaded", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(w, null);
                    w.ResetAllControls();
                }
            }

            return ret;
        }

        /// <summary>
        /// The un hook layers.
        /// </summary>
        private void UnHookLayers()
        {
            this.GetLayersFromReflection();

            for (int i = 0; i < this.layers.Length; i++)
            {
                this.layers[i] = this.UnHookLayer(this.layers[i]);
                GUILayerManager.RegisterLayer(this.layers[i], (GUILayerManager.LayerType)i);
            }

            this.GetLayersFromReflection();
        }

        #endregion
    }
}